#include "program.h"

namespace dw {
	namespace graffiti {

void Program::link(const ProgramAttributeDeclaration* attributesDecl, dw::uint32 attributesDeclCount)
{
	glLinkProgram(m_id);
	
	if (isLinked())
	{
		getAttributesLocation(attributesDecl, attributesDeclCount);
		getUniformsLocation();
	}
	else
	{
		GLint logLength = 0;
		glGetProgramiv(m_id, GL_INFO_LOG_LENGTH, &logLength);

		std::vector<char> logStr(logLength);
		glGetProgramInfoLog(m_id, logLength, NULL, &logStr[0]);

		dwLogWarn("Program link failed %s\n", &logStr[0]);
	}
}

void Program::enableAttributes()
{
	AttributesMap::const_iterator attributesEnd = m_attributes.end();
	for (AttributesMap::const_iterator attributeIt = m_attributes.begin(); attributeIt != attributesEnd; ++attributeIt)
	{
		const InternalAttributeDeclaration& attrDecl = attributeIt->second;

		glEnableVertexAttribArray(attrDecl.location);
	}
}

void Program::disableAttributes()
{
	AttributesMap::const_iterator attributesEnd = m_attributes.end();
	for (AttributesMap::const_iterator attributeIt = m_attributes.begin(); attributeIt != attributesEnd; ++attributeIt)
	{
		const InternalAttributeDeclaration& attrDecl = attributeIt->second;

		glDisableVertexAttribArray(attrDecl.location);
	}
}

bool Program::getAttributeLocation(const AttributeDeclaration& attribute, dw::uint32& location, bool& normalize) const
{
	dwAssert(hasAttribute(attribute), "Invalid parameter: attribute is not known");

	AttributesMap::const_iterator foundIt = m_attributes.find(attribute::semantic::getHashcode(attribute.semantic, attribute.semanticIndex));

	if (foundIt != m_attributes.end())
	{
		 location = foundIt->second.location;
		 normalize = foundIt->second.normalize;

		 return true;
	}

	return false;
}

void Program::getAttributesLocation(const ProgramAttributeDeclaration* attributesDecl, dw::uint32 attributesDeclCount)
{
	GLint attributesCount = 0;
	GLint attributeNamesLength = 0;

	glGetProgramiv(m_id, GL_ACTIVE_ATTRIBUTES, &attributesCount);
	glGetProgramiv(m_id, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &attributeNamesLength);
	std::vector<char> nameStr(attributeNamesLength);

	InternalAttributeDeclaration declaration;
	GLint nameLength = 0;
	GLint size = 0;
	GLenum type;

	for (GLint attributeIt = 0; attributeIt < attributesCount; ++attributeIt)
	{
		glGetActiveAttrib(m_id, attributeIt, attributeNamesLength, &nameLength, &size, &type, &nameStr[0]);
	
		declaration.name = std::string(&nameStr[0]);
		declaration.count = size;
		declaration.normalize = false;
		declaration.location = glGetAttribLocation(m_id, &nameStr[0]);

		const ProgramAttributeDeclaration* decl = NULL;
		for (dw::uint32 declIt = 0; declIt < attributesDeclCount; ++declIt)
		{
			if (attributesDecl[declIt].name == declaration.name)
			{
				decl = &attributesDecl[declIt];

				break ;
			}
		}

		dwAssert(decl != NULL, "A declaration is missing in attributeDecl parameter");
		dwAssert(decl != NULL && type == attribute::glType(decl->declaration.type), "A declaration type does not match program");

		if (decl != NULL && type == attribute::glType(decl->declaration.type))
		{
			dw::uint32 hashcode = attribute::semantic::getHashcode(decl->declaration.semantic, decl->declaration.semanticIndex);
			declaration.declaration = decl->declaration;
			declaration.normalize = decl->normalize;

			m_attributesHashcode |= hashcode;

			m_attributes.insert(std::make_pair(hashcode, declaration));
		}
		else
		{
			dwLogWarn("A declaration is missing in attributeDecl parameter");
		}
	}
}

void Program::getUniformsLocation()
{
	GLint uniformsCount = 0;
	GLint uniformNamesLength = 0;

	glGetProgramiv(m_id, GL_ACTIVE_UNIFORMS, &uniformsCount);
	glGetProgramiv(m_id, GL_ACTIVE_UNIFORM_MAX_LENGTH, &uniformNamesLength);
	std::vector<char> nameStr(uniformNamesLength);

	ProgramUniformDeclaration declaration;
	GLint nameLength = 0;
	GLint size = 0;
	GLenum type;

	for (GLint uniformIt = 0; uniformIt < uniformsCount; ++uniformIt)
	{
		glGetActiveUniform(m_id, uniformIt, uniformNamesLength, &nameLength, &size, &type, &nameStr[0]);
		
		declaration.type = uniform::fromGlType(type);
		declaration.count = size;
		declaration.location = glGetUniformLocation(m_id, &nameStr[0]);

		m_uniforms.insert(std::make_pair(std::string(&nameStr[0]), declaration));
	}
}

	} // namespace graffiti
} // namespace dw
